﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using VersionComparer.Base;
using VersionComparer.Comparer;
using VersionComparer.Configuration;

namespace VersionComparer.DataModel
{
    [DebuggerDisplayAttribute("Text={Text}")]
    [Serializable]
    public abstract class ComparableItem : IComparer<ComparableItem> , IDisposable
    {
        #region fields
        /// <summary>
        /// Tag (for storing related information)
        /// </summary>
        object tag;
        /// <summary>
        /// hash code cache
        /// </summary>
        int? hashcode;
        /// <summary>
        /// error count cache
        /// </summary>
        int? errorCount;
        /// <summary>
        /// ignorable error count cache
        /// </summary>
        int? ignorableErrorCount;
        /// <summary>
        /// ignore errors flag
        /// </summary>
        bool? ignoreErrors;
        /// <summary>
        /// flag indicating if there is a rule applied
        /// </summary>
        bool? isRuleApplied;
        /// <summary>
        /// flag indicating if this item exists (it may be a virtual item just for comparison sake)
        /// </summary>
        bool exists = true;
        /// <summary>
        /// flag indicating if the image is already initialized
        /// </summary>
        bool imageInitialized;
        /// <summary>
        /// Item image
        /// </summary>
        Image image;
        /// <summary>
        /// Parent item
        /// </summary>
        ComparableItem parent;
        /// <summary>
        /// Root item
        /// </summary>
        ComparableItem root;
        /// <summary>
        /// List of rules applied to this item
        /// </summary>
        [NonSerialized]
        ExclusionRules currentRules;
        /// <summary>
        /// Error count
        /// </summary>
        int currentErrorCount;
        /// <summary>
        /// List of subitems (after comparison). It should be similar to childs, but eventually with a different order and with additional virtual items
        /// </summary>
        ComparableItem[] subItems;
        /// <summary>
        /// List of childs
        /// </summary>
        ComparableItem[] childs;
        /// <summary>
        /// description
        /// </summary>
        private string description;
        /// <summary>
        /// text
        /// </summary>
        string text = "";
        #endregion fields

        #region constructors
        public ComparableItem()
        {
            Id = Guid.NewGuid();
        }
        public ComparableItem(ComparableItem parent)
            : this()
        {
            this.parent = parent;
        }

        #endregion constructors

        #region properties
        /// <summary>
        /// Percentage of the parent represented by this child
        /// </summary>
        public double Percentage
        {
            get;
            set;
        }
        /// <summary>
        /// Flag indicating if this is initialized
        /// </summary>
        public virtual bool Initialized
        {
            get
            {
                return SubItems != null;
            }
        }
      
        /// <summary>
        /// Index of the item when loaded (before sorting)
        /// </summary>
        internal int OriginalIndex
        {
            get;
            set;
        }
        /// <summary>
        /// Full text for this item
        /// </summary>
        public virtual string FullText
        {
            get;
            set;
        }
        /// <summary>
        /// Index (after comparison)
        /// </summary>
        public int Index
        {
            get;
            set;
        }
        /// <summary>
        /// Image
        /// </summary>
        public Image Image
        {
            get
            {
                if (!imageInitialized)
                {
                    if (image == null)
                        image = GetImage();
                    imageInitialized = true;
                    //if (image == null && RelatedItem != null)
                    //    image = RelatedItem.Image;
                }
                return image;
            }
            set
            {
                image = value;
                imageInitialized = true;
            }
        }
        /// <summary>
        /// Flag indicating if this item has changed since last refresh
        /// </summary>
        public virtual bool HasChanged
        {
            get
            {
                return false;
            }
        }
        /// <summary>
        /// Difference after the comparison
        /// </summary>
        public virtual Difference Difference
        {
            get;
            set;
        }
        /// <summary>
        /// Parent
        /// </summary>
        public virtual ComparableItem Parent
        {
            get
            {
                return parent;
            }
            set
            {
                parent = value;
                root = null;
            }
        }
        /// <summary>
        /// Item that was compared with this item
        /// </summary>
        public virtual ComparableItem RelatedItem
        {
            get;
            set;
        }
        /// <summary>
        /// Subitems (childs after comparison)
        /// </summary>
        public virtual ComparableItem[] SubItems
        {
            get
            {
                return subItems;
            }
            set
            {
               
                subItems = value;
                if (value != null)
                    for (int i = 0; i < value.Length; i++)
                        value[i].Index = i;
            }
        }
        /// <summary>
        /// Error count
        /// </summary>
        public virtual int ErrorCount
        {
            get
            {
                if (SubItems == null)
                    return 0;
                if (errorCount == null)
                {
                    errorCount = 0;
                    if (!IgnoreErrors)
                    {
                        if (Difference != Difference.None)
                            errorCount++;

                        foreach (var subItem in SubItems)
                            errorCount += subItem.ErrorCount;
                    }
                }
                return errorCount.Value;
            }
            protected set
            {
                if (value < 0)
                    errorCount = null;
                else
                    errorCount = value;
            }
        }
        /// <summary>
        /// Ignorable errors count
        /// </summary>
        public virtual int IgnorableErrorCount
        {
            get
            {
                if (SubItems == null)
                    return 0;
                if (ignorableErrorCount == null)
                {
                    ignorableErrorCount = 0;

                    if (Difference != Difference.None && IgnoreErrors)
                        ignorableErrorCount++;
                    foreach (var item in SubItems)
                    {
                        ignorableErrorCount += item.IgnorableErrorCount;
                        if (IgnoreErrors)
                            ignorableErrorCount += item.ErrorCount;
                    }
                }
                return ignorableErrorCount.Value;
            }
            set
            {
                if (value < 0)
                    ignorableErrorCount = null;
                else
                    ignorableErrorCount = value;
            }
        }
        /// <summary>
        /// Flag indicating if errors should be ignored (exclusion rules are applied on this item or a parent)
        /// </summary>
        public virtual bool IgnoreErrors
        {
            get
            {
                if (ignoreErrors == null)
                {
                    if (Difference == Difference.ValueCase && Settings.Current.IgnoreCase == CaseDifferences.Ignore)
                        ignoreErrors = true;
                    else
                    {
                        if (IsExludedByRule())
                            ignoreErrors = true;
                        else
                            ignoreErrors = false;
                    }
                }
                return ignoreErrors.Value;
            }
            protected set
            {
                ignoreErrors = value;
            }
        }
        /// <summary>
        /// Indicates if a rule is applied
        /// </summary>
        public virtual bool IsRuleApplied
        {
            get
            {
                if (isRuleApplied == null)
                {
                    isRuleApplied = false;
                    foreach (var rule in CurrentRules)
                    {
                        if (rule.WasCreatedOverNode(this))
                        {
                            isRuleApplied = true;
                            break;
                        }
                    }
                    if (!isRuleApplied.Value && RelatedItem != null)
                        isRuleApplied |= RelatedItem.IsRuleApplied;
                }

                return isRuleApplied.Value;
            }
        }
        /// <summary>
        /// Current error count (updated whenever an error is found, while ErrorCount is the final error count)
        /// </summary>
        public int CurrentErrorCount
        {
            get
            {
                return currentErrorCount;
            }
        }
        /// <summary>
        /// Unique ID
        /// </summary>
        public Guid Id
        {
            get;
            set;
        }
        /// <summary>
        /// hash code used for matching this item with some other. different hash codes means that two items are different - equal hash codes must be validated before being accepted as a match.
        /// </summary>
        public int MatchHashCode
        {
            get
            {
                if (hashcode == null)
                    hashcode = HashCodeForMatch;
                return hashcode.Value;
            }
        }
        /// <summary>
        /// hash code used for matching this item with some other. different hash codes means that two items are different - equal hash codes must be validated before being accepted as a match.
        /// </summary>
        protected virtual int HashCodeForMatch
        {
            get
            {
                string fullText = FullText;
                if (fullText == null)
                    return 0;
                return fullText.GetHashCode();

            }
        }
        /// <summary>
        /// Description
        /// </summary>
        public virtual string Description
        {
            get
            {
                int currentErrorCount = Root.CurrentErrorCount;
                if (currentErrorCount >= Settings.Current.MaxErrorCount)
                    return string.Format(Resources.Resources.MaxErrorsCountReached, currentErrorCount);
                else
                {
                    if (!string.IsNullOrEmpty(description))
                        return description;
                    switch (Difference)
                    {
                        case Difference.OutOfOrder:
                            return string.Format(Resources.Resources.ItemOutOfOrder);
                        case Difference.Unexistant:
                            return string.Format(Resources.Resources.ItemUnexistant);
                        case Difference.Name:
                            return string.Format(Resources.Resources.ItemNameIsDifferent);
                        case Difference.Value:
                            return string.Format(Resources.Resources.ItemValueIsDifferent);
                        case Difference.ValueCase:
                            return string.Format(Resources.Resources.ItemValueIsDifferentInCase);
                        case Difference.Unmatched:
                            return string.Format(Resources.Resources.ItemUnmatched);
                        case Difference.TooManyErrors:
                            return string.Format(Resources.Resources.MaxErrorsReached);
                        default:
                            return "";
                    }
                }
            }
            set
            {
                description = value;
            }

        }
        /// <summary>
        /// Text
        /// </summary>
        public virtual string Text
        {
            get
            {
                return text;
            }
            set
            {
                text = value;
            }
        }
        /// <summary>
        /// True if the item exists (is not a virtual item created only for comparison)
        /// </summary>
        public virtual bool Exists
        {
            get
            {
                return exists;
            }
            set
            {
                exists = value;
            }
        }
        /// <summary>
        /// True if item's value may be changed
        /// </summary>
        public virtual bool AllowsChangingValue
        {
            get
            {
                return true;
            }
        }
        /// <summary>
        /// List of rules applied to this item
        /// </summary>
        public virtual ExclusionRules CurrentRules
        {
            get
            {
                if (currentRules == null)
                {
                    if (Parent != null)
                        currentRules = Root.CurrentRules;
                    if (currentRules == null)
                        currentRules = new ExclusionRules();
                }
                return currentRules;
            }
            set
            {
                currentRules = value;
            }
        }

        /// <summary>
        /// True if the item must be updated
        /// </summary>
        public virtual bool RequiresUpdate
        {
            get { return true; }
        }

        /// <summary>
        /// Item's tag
        /// </summary>
        public object Tag
        {
            get
            {
                return tag;
            }
            set
            {
                tag = value;
            }
        }

   
        /// <summary>
        /// Related item's file path
        /// </summary>
        public virtual string RelatedFilePath
        {
            get
            {
                return Root.RelatedFilePath;
            }
        }

        /// <summary>
        /// If true the element may be copied to the compared item
        /// </summary>
        public virtual bool AllowsCopying
        {
            get { return true; }
        }
        /// <summary>
        /// Root element
        /// </summary>
        public virtual ComparableItem Root
        {
            get
            {
                if (root == null)
                {
                    if (Parent == null)
                        root = this;
                    else
                        root = Parent.Root;
                }
                return root;
            }
        }

        #endregion properties

        #region methods
        /// <summary>
        /// returns the image for this item
        /// </summary>
        /// <returns>image for this item</returns>
        protected virtual Image GetImage()
        {
            return null;
        }
        /// <summary>
        /// Changes the value for the current item
        /// </summary>
        /// <param name="newValue">new value (serialized as string)</param>
        /// <returns>true if value changed, false otherwise</returns>
        public virtual bool ChangeValue(string newValue)
        {
            return false;
        }

        #region exclusion rules related
        /// <summary>
        /// Clears the list of rules applied to this item
        /// </summary>
        public virtual void ResetRules()
        {
            currentErrorCount = 0;
            //currentRules = null;
            isRuleApplied = null;
            errorCount = null;
            ignorableErrorCount = null;
            ignoreErrors = null;
            if (SubItems != null)
            {
                foreach (var item in SubItems)
                    item.ResetRules();
            }
        }
        /// <summary>
        /// returns true if a rule excludes this item
        /// </summary>
        /// <returns></returns>
        public virtual bool IsExludedByRule()
        {
            foreach (var exclusionRule in Root.CurrentRules)
                if (exclusionRule.AppliesToNode(this))
                    return true;
            return false;
        }
        #endregion exclusion rules related

        #region matching related
        /// <summary>
        /// Returns a double representing the level of mathing relative to the other item.
        /// </summary>
        /// <param name="algorithm">comparison algorithm to use</param>
        /// <param name="other">other item to match with</param>
        /// <returns>0 for no match, 1 for certain match, any value in between for a level of likeness</returns>
        public virtual double Matches(ComparisonAlgorithm algorithm, ComparableItem other)
        {
            return other.FullText == FullText ? Match.CertainMatch : Match.MatchStrings(algorithm , other.Text, Text, false);
        }
        /// <summary>
        /// Checks if this item is different from another item
        /// </summary>
        /// <param name="otherItem">other item to compare to</param>
        /// <returns>type of difference</returns>
        public virtual Difference DifferenceRelativeTo(ComparableItem otherItem)
        {
            if ( Exists != otherItem.Exists)
            {
                if ( Exists )
                    return Difference.Unmatched;
                else
                    return Difference.Unexistant;
            }
            else
                return Text == otherItem.Text ? Difference.None : string.Compare(Text, otherItem.Text, true) == 0 ? Difference.ValueCase : Difference.Value;
        }
        #endregion matching related
        /// <summary>
        /// Returns true if this value contains all the strings being searched. Used by search engine
        /// </summary>
        /// <param name="ignoreCase">if true case is ignored in the comparison</param>
        /// <param name="valuesBeingSearched">values being searched</param>
        /// <returns>true if all the values exist in the current item</returns>
        public virtual bool ContainsString(bool ignoreCase, params string[] valuesBeingSearched)
        {
            return Text.FindInString(valuesBeingSearched, ignoreCase);
        }
        /// <summary>
        /// Saves the item to file (after being changed). In principle only root items can be saved - this save notification should be propagated up until the root
        /// </summary>
        /// <returns></returns>
        public virtual bool Save()
        {
            if (Parent != null)
                return Parent.Save();
            return false;
        }
        /// <summary>
        /// Copies the current item to the related item (and optionally saves the root item)
        /// </summary>
        /// <param name="save">if true the root item is saved</param>
        /// <returns>true if the value is copied and saved, false otherwise</returns>
        public virtual bool Copy(bool save)
        {
            return false;
        }
        /// <summary>
        /// Gets the sibling with index
        /// </summary>
        /// <param name="index">index of the sibling</param>
        /// <returns>item that is sibling and has index index</returns>
        public ComparableItem Sibling(int index)
        {
            if (Parent == null || index < 0 || index >= Parent.SubItems.Length)
                return null;
            return Parent.SubItems[index];
        }
        /// <summary>
        /// Called when the comparison has terminated for this item
        /// </summary>
        public virtual void ProcessFinished()
        {
            FullText = null;

        }
        /// <summary>
        /// increments the number of errors
        /// </summary>
        /// <returns>current error count</returns>
        public int IncrementErrorCount()
        {
            return Interlocked.Increment(ref currentErrorCount);
        }
        /// <summary>
        /// Creates the childs for this item. Should be overriden by non-abstract inheritors, but by default it has no childs
        /// </summary>
        /// <returns></returns>
        protected virtual ComparableItem[] CreateChilds()
        {
            return new ComparableItem[0];
        }

        /// <summary>
        /// Creates the childs if they were not created yet
        /// </summary>
        /// <returns></returns>
        public ComparableItem[] GetChilds()
        {
            if (childs == null)
                childs = CreateChilds();
            return childs;
        }


        #region sorting related
        /// <summary>
        /// Compares this item to another for sorting reasons. Used for sorting in collections
        /// </summary>
        /// <param name="other">other item to compare with</param>
        /// <returns>0 for "equal", 1 for "bigger than", -1 for "lower than"</returns>
        protected virtual int CompareToItem(ComparableItem other)
        {
            return other.Text.CompareTo(Text);
        }
        /// <summary>
        /// Compare to another item. If they are equal, return the index comparison
        /// </summary>
        /// <param name="other">other item to compare to</param>
        /// <returns>0 for "equal", 1 for "bigger than", -1 for "lower than"</returns>
        public int CompareTo(ComparableItem other)
        {
            int comparisonResult = CompareToItem(other);
            if (comparisonResult != 0)
                return comparisonResult;
            return Index.CompareTo(other.Index);
        }
        /// <summary>
        /// Compares two items for sorting
        /// </summary>
        /// <param name="x">item1 to compare</param>
        /// <param name="y">item2 to compare</param>
        /// <returns>0 for "equal", 1 for "bigger than", -1 for "lower than"</returns>
        public int Compare(ComparableItem x, ComparableItem y)
        {
            int result = (x as ComparableItem).CompareTo(y);
            result = result > 0 ? 1 : result == 0 ? 0 : -1;
            return result;
        }
        #endregion sorting related

        #region rtf related
        /// <summary>
        /// Builds this items (and its childs) Rtf
        /// </summary>
        /// <param name="builder">Rtf builder</param>
        /// <param name="differencesOnly">build rtf with differences only</param>
        /// <param name="useParentColor">use parent color or overwrite with own color</param>
        protected virtual void BuildRtf(RtfBuilder builder, bool differencesOnly, bool useParentColor)
        {
            var childs = differencesOnly ? SubItems : GetChilds();
            if (childs != null)
            {
                builder.Ident();
                foreach (var child in childs)
                    child.GetAsRtf(builder, differencesOnly, true, useParentColor);
                builder.DeIdent();
            }

        }
        /// <summary>
        /// Gets the current item's Rtf
        /// </summary>
        /// <param name="builder">Rtf builder</param>
        /// <param name="differencesOnly">build rtf with differences only</param>
        /// <param name="applyHightlight">if true highlighting is applied</param>
        /// <param name="useParentColor">use parent color or overwrite with own color</param>
        private void GetAsRtf(RtfBuilder builder, bool differencesOnly, bool applyHightlight, bool useParentColor)
        {
            builder.SetTag(this);
            Color? previousBackColor = builder.BackColor;
            Difference diff = Difference;
            if (previousBackColor != null)
            {
            }
            switch (diff)
            {
                case Difference.None:
                    if (RelatedItem != null && !useParentColor)
                        builder.BackColor = null;
                    break;
                case Difference.Unmatched:
                    if (applyHightlight)
                        builder.BackColor = Settings.Current.UnmatchedBackColor;
                    break;
                case Difference.Unexistant:
                    RtfBuilder otherBuilder = new RtfBuilder();
                    otherBuilder.BackColor = Settings.Current.UnexistantBackColor;
                    (RelatedItem as ComparableItem).GetAsRtf(otherBuilder, differencesOnly, false, false);
                    if (applyHightlight)
                        builder.BackColor = Settings.Current.UnexistantBackColor;

                    otherBuilder.DimColors(Settings.Current.UnmatchedDimPercentage);
                    builder.Append(otherBuilder);
                    builder.BackColor = previousBackColor;
                    return;
                case Difference.OutOfOrder:
                    if (applyHightlight)
                    {
                        useParentColor = true;
                        builder.BackColor = Settings.Current.OutOfOrderBackColor;
                    }

                    break;
                default:
                    if (applyHightlight)
                        builder.BackColor = Settings.Current.DifferentBackColor;
                    break;
            }
            BuildRtf(builder, differencesOnly, useParentColor);
            if (applyHightlight)
                builder.BackColor = previousBackColor;
        }
        /// <summary>
        /// Gets the current item's Rtf
        /// </summary>
        /// <param name="builder">Rtf builder</param>
        /// <param name="differencesOnly">build rtf with differences only</param>
        /// <param name="useParentColor">use parent color or overwrite with own color</param>
        public void GetAsRtf(RtfBuilder builder, bool differencesOnly, bool useParentColor)
        {
            GetAsRtf(builder, differencesOnly, true, useParentColor);
        }
        #endregion rtf related

        /// <summary>
        /// Refreshes the item
        /// </summary>
        public virtual void Refresh()
        {
            SubItems = null;
            childs = null;
            ResetRules();
        }
        #region dispose related
        /// <summary>
        /// Disposes the item
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        /// <summary>
        /// Disposes the item
        /// </summary>
        /// <param name="disposing"></param>
        private void Dispose(bool disposing)
        {
            RelatedItem = null;
        }
        #endregion dispose related


        #endregion methods

    }
}
